/**
 * Copyright 2008 Mathias Doenitz, http://lis.to/
 *
 * This file is part of the lis.to java desktop client. The lis.to java desktop client is free software: you can
 * redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any later version.
 *
 * The lis.to java desktop client is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with the lis.to java desktop client.
 * If not, see http://www.gnu.org/licenses/
 */

package listo.client.tasklists;

import com.google.common.collect.Lists;
import com.google.common.collect.PrimitiveArrays;
import com.google.inject.Inject;
import listo.client.Convert;
import listo.client.MenuBuilder;
import listo.client.actions.Actions;
import listo.client.model.ObjectId;
import listo.client.model.Task;
import listo.client.model.Tasks;
import listo.utils.MiscUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.List;

public abstract class TaskList extends JList {

    protected final Actions actions;
    protected final MenuBuilder menuBuilder;

    protected Tasks tasks;

    @Inject
    public TaskList(final TaskListRenderer renderer, Actions actions, MenuBuilder menuBuilder,
                    TaskListManager taskListManager) {
        this.actions = actions;
        this.menuBuilder = menuBuilder;

        setCellRenderer(renderer);

        addListSelectionListener(taskListManager);

        MouseAdapter mouseListener = new MouseAdapter() {
            public void mousePressed(MouseEvent e) {
                if (!isEnabled()) return;
                if (e.isPopupTrigger()) {
                    createPopupMenu(e);
                    return;
                }
                if (e.getClickCount() == 2) {
                    Task task = TaskList.this.getTaskAt(e.getPoint());
                    if (e.getSource() instanceof MainFormTaskList && task == null) {
                        TaskList.this.actions.addTaskAction.actionPerformed(new ActionEvent(e, 0, "DoubleClick"));
                    } else {
                        TaskList.this.actions.editTaskAction.actionPerformed(new ActionEvent(e, 0, "DoubleClick"));
                    }
                }
            }

            public void mouseReleased(MouseEvent e) {
                if (isEnabled() && e.isPopupTrigger()) createPopupMenu(e);
            }

            public void mouseClicked(MouseEvent e) {
                if (!isEnabled()) return;
                Task task = TaskList.this.getTaskAt(e.getPoint());
                if (renderer.isInCheckBox(e.getPoint())) {
                    TaskList.this.actions.toggleTasksCompletedAction
                            .actionPerformed(new ActionEvent(TaskList.this, 0, "CheckBoxClick"));
                } else if (renderer.isInNoteIcon(task, e.getPoint())) {
                    TaskList.this.actions.editTaskAction.actionPerformed(new ActionEvent(e, 0, "EditNote"));
                } else {
                    String url = renderer.isInUrl(task, e.getPoint());
                    if (StringUtils.isNotEmpty(url)) MiscUtils.openBrowser(url);
                }
            }

            public void mouseMoved(MouseEvent e) {
                Point point = e.getPoint();
                Task task = getTaskAt(point);
                boolean handCursor = task != null && (renderer.isInCheckBox(point) ||
                        renderer.isInNoteIcon(task, point) || StringUtils.isNotEmpty(renderer.isInUrl(task, point)));
                Cursor cursor = Cursor.getPredefinedCursor(handCursor ? Cursor.HAND_CURSOR : Cursor.DEFAULT_CURSOR);
                if (getCursor() != cursor) setCursor(cursor);
            }
        };
        addMouseListener(mouseListener);
        addMouseMotionListener(mouseListener);

        addKeyListener(new KeyAdapter() {
            public void keyPressed(KeyEvent e) {
                if (!isEnabled()) return;
                if (e.getKeyCode() == KeyEvent.VK_SPACE) {
                    TaskList.this.actions.toggleTasksCompletedAction.actionPerformed(null);
                } else if (e.getKeyCode() == KeyEvent.VK_DELETE) {
                    if (TaskList.this.actions.detachCurrentFoldersAction.isEnabled()) {
                        TaskList.this.actions.detachCurrentFoldersAction.actionPerformed(null);
                    } else if (TaskList.this.actions.deleteTasksAction.isEnabled()) {
                        TaskList.this.actions.deleteTasksAction.actionPerformed(null);
                    }
                }
            }
        });

        addFocusListener(new FocusAdapter() {
            public void focusGained(FocusEvent e) {
                if (getSelectedIndex() == -1) setSelectedIndex(0);
            }
        });
    }

    public void setTasks(Tasks tasks) {
        this.tasks = tasks;

        ObjectId[] selectedTasks = Convert.toObjectIds(getSelectedValues());

        super.setListData(tasks.toArray(new Task[tasks.size()]));

        setSelectedTasks(selectedTasks);
    }

    public void setSelectedTasks(ObjectId[] selectedTasks) {
        List<Integer> newSelection = Lists.newArrayList();
        for (ObjectId taskId : selectedTasks) {
            int ix = indexOf(taskId);
            if (ix >= 0) newSelection.add(ix);
        }
        setSelectedIndices(PrimitiveArrays.toIntArray(newSelection));
    }

    public Task getTaskAt(int index) {
        if (index < 0 || index >= tasks.size()) return null;
        return tasks.get(index);
    }

    public Task getTaskAt(Point point) {
        int clickIx = locationToIndex(point);
        return clickIx != -1 && getCellBounds(clickIx, clickIx).contains(point) ? getTaskAt(clickIx) : null;
    }

    public int indexOf(Task task) {
        return tasks.indexOf(task);
    }

    public int indexOf(ObjectId taskId) {
        for (int i = 0; i < tasks.size(); i++) {
            if (tasks.get(i).getId().equals(taskId)) return i;
        }
        return -1;
    }

    public int[] indicesOf(Tasks dragTasks) {
        int[] ixs = new int[dragTasks.size()];
        for (int i = 0; i < dragTasks.size(); i++) {
            ixs[i] = indexOf(dragTasks.get(i));
        }
        return ixs;
    }

    public boolean isEmpty() {
        return tasks.isEmpty();
    }

    protected abstract void createPopupMenu(MouseEvent e);

    protected void createPopupMenu(MouseEvent e, Action... actions) {
        int clickIndex = locationToIndex(e.getPoint());

        // if the user clicked on a task that is not part of the current selection
        // we select that task before opening the popup menu
        if (!ArrayUtils.contains(getSelectedIndices(), clickIndex)) setSelectedIndex(clickIndex);

        menuBuilder.createPopupMenu(actions).show(this, e.getX(), e.getY());
    }
}
